Nesta aula apresentaremos noções básicas da linguagem Scala. Nos exemplos abaixo, abordaremos:
Para espantar a má sorte, comecemos com o básico:
In [ ]:
print("Olá Mundo!")
In [ ]:
val valor = 10
Como valores são imutáveis, não podemos realizar uma segunda atribuição nele. Quando tentamos atribuir uma nova informação, obtemos o erro de reassigment to val:
In [ ]:
valor = 20
In [ ]:
var variavel = 10
Diferente dos valores, podemos alterar a informação da variável:
In [ ]:
variavel = 20
print(variavel)
Porém, nunca podemos mudar o tipo da variável. Caso o façamos, obtemos um erro de type mismatch.
In [ ]:
variavel = "string"
Quando vamos atribuir algo a um val ou var, podemos escrever um bloco de código. Blocos de código podem retornar informações em Scala.
In [ ]:
val x = 1
val y = {
val a = 10
a + x
}
In [ ]:
var x: Int = 10
O tipo é definido durante a primeira atribuição. Como em valores realizamos apenas uma atribuição, não é necessário colocar tipo, porém, se quisermos, podemos forçar uma variável a ser de um certo tipo:
In [ ]:
var inteiro: Int
inteiro = "string"
Os seguintes tipos são os mais utilizados em Scala:
In [ ]:
val inteiro = 1
val decimal = 1.5
val booleano = true
val char = 'c'
val string = "string"
In [ ]:
var any:Any = 1
any = 1.5
any = true
any = 'c'
any = "string"
In [ ]:
// Um array de dez inteiros, inicializado com zeros
val nums = new Array[Int](10)
// Um array de string, inicializado com nulls
val a = new Array[String](10)
// Podemos também definir um array informando seus conteúdos ao invés de tamanho
val s = Array("Hello", "World")
// OBS: qunado informa-see os elementos do array, não utilizamos new
// Em Scala, utilizamos () ao invés de [] para acessar os elementos de um array.
s(0) = "Goodbye"
Quando inicializamos um array com mais de um tipo de conteúdo, seu tipo é definido por uma classe mais genérica:
In [ ]:
val numeros = Array(1,2.0,3e-2)
val misturado = Array(1,'a',2.0,"bc")
Para definir arrays multidimensionais, precisamos especificar suas dimensões.
In [ ]:
val matriz2d = Array.ofDim[Int](2,2)
val matriz3d = Array.ofDim[Int](2,2,2)
In [ ]:
val x = 11
if(x%2==0){
print(x+ " é par")
}
else{
print(x+ " é ímpar")
}
Como Scala é funcional, podemos usar controle de fluxo para retornar valores:
In [ ]:
val x = 11
var paridade = if(x%2==0) "par" else "ímpar"
print("x é "+paridade)
In [ ]:
println("for com until")
for(i <- 1 until 10){
print(i+" ")
}
println('\n')
println("for com to")
for(i <- 1 to 10){
print(i+" ")
}
Quando precisamos fazer laços aninhados (um loop dentro de outro) normalmente utilizamos 2 for diferentes.
In [ ]:
for(i <- 1 to 3)
for(j <- 1 to 3)
print("("+i+","+j+") ")
Em Scala, podemos usar for comprehensions. Em uma única definição de for podemos definir a combinação de laços na qual o bloco de comando será executado:
In [ ]:
for{
i <- 1 to 3
j <- 1 to 3
}{
print("("+i+","+j+") ")
}
Essa compressão também permite definirmos condições sobre os valores do loop.
In [ ]:
for{
i <- 1 to 3
if i%2==1
j <- 1 to 3
if(j>=i)
}{
print("("+i+","+j+") ")
}
Como o for itera sobre uma sequência, podemos fazê-lo iterar sobre um Array
In [ ]:
val pares = Array(2,4,6,8)
for(par <- pares){
print(par+" ")
}
Como vimos anteriormente, blocos de comando podem gerar valores, desque estes sejam a última informação do bloco. Scala contém um operador chamado yield que permite que o bloco do for retorne uma estrutura análoga ao array: um IndexedSeq. Por hora, podemos tratar essa estrutura como um array.
In [ ]:
val pares = for{
i <- 0 to 10
if i%2 == 0
} yield {
i
}
In [ ]:
var i = 1
while(i<=10){
print(i+" ")
i = i + 1
}
In [ ]:
var i = 0
do {
print(i+" ")
i = i+1
}while(i<10)
In [ ]:
def soma1(x: Int): Int = {
return x + 1
}
print(soma1(10))
Uma função que não tem retorno é do tipo Unit (semelhante ao void em Java).
In [ ]:
def mostra(x: Any): Unit = {
println(x)
}
mostra(10)
Assim como em estruturas de controle e laços, não precisamos colocar o código da função entre parênteses quando for uma função de apenas uma linha de código.
In [ ]:
def soma1(x: Int): Int = return x+1
print(soma1(10))
Não precisamos utilizar return para retornar um valor, basta que esse valor seja escrito no fim da função.
In [ ]:
def soma1(x: Int): Int = x+1
print(soma1(10))
Não precisamos determinar o tipo do retorno de uma função, pois o compilador é capaz de inferir esse tipo.
In [ ]:
def soma1(x: Int) = x+1
print(soma1(10))
Quando uma função pode ter mais de um tipo de retorno, o compilador define o retorno da função como um tipo intermediário.
In [ ]:
def f(x: Int) = if(x>0) 1 else 0.0
val x = f(0)
val y = f(1)
In [ ]:
def f(x: Int) = if(x>0) 1 else "menor ou igual à 0"
val x = f(0)
val y = f(1)
Em funções recursivas, é necessário definir o tipo, pois é uma operação muito custosa inferir todas as possibilidades de retorno de uma função recursiva.
In [ ]:
def mostraRecursivo(x: Int) = {
if(x>0){
print(x+", ")
mostraRecursivo(x-1)
}
else print(x)
}
mostraRecursivo(10)
In [ ]:
def mostraRecursivo(x: Int): Unit = {
if(x>0){
print(x+", ")
mostraRecursivo(x-1)
}
else print(x)
}
mostraRecursivo(10)
Um objeto é uma estrutura que carrega consigo informações(atributos) e comportamentos(métodos). Objetos são únicos e não podem ser sobrescritos.
Podemos escrever um objeto em Scala como um código qualquer. Nesse código:
Para definir um objeto em Scala basta utilizar a seguinte sintaxe:
In [ ]:
object Contador{ //nome do objeto
var numero = 10 //um atributo que carrega a informação de um número inteiro
def valor = numero //um método que retorna o valor de "numero"
def tick = { //um método que decrementa o valor de número
numero -= 1
}
def reset = { //um método que reseta o valor de "numero" para 10
numero = 10
}
}
println(Contador.valor)
Contador.tick
println(Contador.valor)
Contador.tick
println(Contador.valor)
Contador.reset
println(Contador.valor)
In [ ]:
class Pessoa {
var nome: String = null
var cpf: String = null
}
val mario = new Pessoa
mario.nome = "Mario"
mario.cpf = "060.000.000-00"
println(mario.nome)
println(mario.cpf)
OBS: Por padrão, todos os métodos e atributos de uma classe em Scala são públicos. Para defini-los como privados, basta utilizar o modificador private
OBS2: Atributos definidos como val são apenas para leitura, enquanto os definidos como var são para leitura e escrita
Podemos definir um método chamado toString para que, quando chamarmos a função print, seja feita uma apresentação mais legível do objeto instanciado.
Para implementar esse método em Scala, precisamos fazer uma sobrescrita, tendo de adicionar o modificador override antes do nome do método.
In [ ]:
class Pessoa(nome: String, cpf: String){
def this(nome: String) = this(nome, "Não cadastrado")
def getNome = nome
def getCPF = cpf
override def toString = "Nome: "+nome+", CPF: "+cpf
}
val mario = new Pessoa("Mário")
print(mario)
In [ ]:
val x = 10
//podemos chamar um método como uma operação, usando uma notação mais limpa
println(x + 10)
//e podemos também chamar um método pela notação padrão, utilizando ponto + nome do método + argumentos
println(x.+(10))
Para exemplificar o uso de operadores, vamos definir uma classe que representa os números Racionais em forma de fração, definindo métodos que nos permitem operar entre eles:
In [ ]:
class Racional(n: Int, d: Int){
//declaramos essas variáveis para tornar essas informações como públicas
//utilizamos val para evitar sobrescrita
val numerador = n
val denominador = d
def somar(b: Racional): Racional =
new Racional(numerador*b.denominador + b.numerador * denominador, denominador*b.denominador)
def subtrair(b: Racional): Racional =
new Racional(numerador*b.denominador - b.numerador * denominador, denominador*b.denominador)
override def toString: String = numerador.toString+"/"+denominador.toString
}
val metade = new Racional(1,2)
val terco = new Racional(1,3)
println("soma: "+metade.somar(terco))
println("subtração: "+metade.subtrair(terco))
Podemos reescrever esses métodos como os seguintes operadores: + e -:
In [ ]:
class Racional(n: Int, d: Int){
//declaramos essas variáveis para tornar essas informações como públicas
//utilizamos val para evitar sobrescrita
val numerador = n
val denominador = d
def + (b: Racional): Racional =
new Racional(numerador*b.denominador + b.numerador * denominador, denominador*b.denominador)
def - (b: Racional): Racional =
new Racional(numerador*b.denominador - b.numerador * denominador, denominador*b.denominador)
override def toString: String = numerador.toString+"/"+denominador.toString
}
val metade = new Racional(1,2)
val terco = new Racional(1,3)
println("soma: "+(metade + terco))
println("subtração: "+(metade - terco))
In [ ]:
def fib(n: Int): Int =
if(n == 0) 0
else if (n == 1) 1
else fib(n-1) + fib(n-2)
for(i <- 0 to 10) print(fib(i)+" ")
In [ ]:
def domino: Unit =
for{
i <- 0 to 6
j <- i to 6
} print(s"($i,$j) ")
domino
In [ ]:
class Matrix(m: Int, n: Int){
def this(n: Int) = this(n,n)
val matrix = Array.ofDim[Double](m,n)
def get(i: Int, j: Int): Double = matrix(i)(j)
//O método apply() permite que o objeto seja chamado como uma função.
//Como estamos implementando uma matriz, seria interessante que possamos acessar
//seus valores fazendo uma chamada como A(i,j).
def apply(i: Int, j: Int): Double = get(i,j)
def set(i: Int, j: Int, x: Double): Unit =
matrix(i)(j) = x
//O método update() é análogo ao apply, porém ele serve apra atribuições.
//Para a matriz, é interessante que possamos alterar o valor de um elemento direto pelo índice,
//como A(i,j) = 10.0 .
def update(i: Int, j: Int, x: Double): Unit = set(i,j,x)
def +(b: Matrix): Matrix = {
val c = new Matrix(m,n)
for{
i <- 0 until m
j <- 0 until n
} c(i,j) = this(i,j) + b(i,j)
c
}
def -(b: Matrix): Matrix = {
val c = new Matrix(m,n)
for{
i <- 0 until m
j <- 0 until n
} c(i,j) = this(i,j) - b(i,j)
c
}
override def toString(): String = {
var result = ""
for(i <- 0 until m){
result += "["+matrix(i).mkString(",")+"]\n"
}
result
}
}
val m1 = new Matrix(2)
m1(0,0) = 10
val m2 = new Matrix(2)
m2(1,1) = 20
println(m1+m2)
println(m1-m2)
In [ ]:
object MatrixHelper{
def generateDiagonal(n: Int, x: Double): Matrix = {
val I = new Matrix(n)
for (i <- 0 until n) I(i,i) = x
I
}
//Uma matriz identidade é uma matriz diagonal preenchida com 1's !
def generateIdentity(n: Int): Matrix =
generateDiagonal(n, 1)
}
println(s"Matriz diagonal (2,2) com 5:\n${MatrixHelper.generateDiagonal(2,5)}\n")
println(s"Matriz identidade (3,3):\n${MatrixHelper.generateIdentity(3)}")